import { Injectable } from '@nestjs/common';
import { type } from 'os';
import { ObjectLiteral, QueryRunner, Repository, Connection, FindConditions, EntityTarget, FindManyOptions, ObjectID } from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';

type Where<T> = FindConditions<T>[] | FindConditions<T> | ObjectLiteral | string;


@Injectable()
export class BaseModelService<T = ObjectLiteral> {
    protected primaryKey:keyof T;
    protected connection:Connection;
    public queryRunner:QueryRunner;
    protected repository:Repository<T>;

    async startTransaction() {
        this.queryRunner = await this.connection.createQueryRunner()
        await this.queryRunner.connect();
        await this.queryRunner.startTransaction();
    }

    async commitTransaction() {
        await this.queryRunner.commitTransaction()
    }

    async rollbackTransaction() {
        await this.queryRunner.rollbackTransaction();
    }

    async release() {
        await this.queryRunner.release();
    }

    async querySql<E>(query: string) : Promise<E> {
        let res = await this.repository.query(query)
        return res
    }

    async count(where? : Where<T>) {
        let total = await this.repository.count({
            where : where
        })
        
        return total
    }

    async find(where? : Where<T>, select? : Array<keyof T> | null, skip? : number, take? : number, order? : {[key in keyof T]? : 'ASC' | 'DESC'}) {
        let options:FindManyOptions<T> = {
            
        }
        if(where) {
            options.where = where
        }
        if(select) {
            options.select = select
        }
        if(skip) {
            options.skip = skip
        }
        if(take) {
            options.take = take
        }
        if(order) {
            options.order = order
        }

        let res = await this.repository.find(options)
        return res
    }

    async findOne(where? : Where<T>, select? : Array<keyof T>, order? : {[key in keyof T] : 'ASC' | 'DESC'}) {
        let res = await this.repository.findOne({
            select : select,
            where : where,
            order : order
        })

        return res ? res : null
    }

    async insert(data : QueryDeepPartialEntity<T> | (QueryDeepPartialEntity<T>[])) : Promise<number | null> {
        let res = await this.repository.insert(data)
        if(res) {
            let raw = res.raw
            if(raw && raw.affectedRows > 0) {
                return raw.insertId
            }
        }
        
        return null
    }

    async update(where: string | string[] | number | number[] | Date | Date[] | ObjectID | ObjectID[] | FindConditions<T>, partialEntity: QueryDeepPartialEntity<T>) : Promise<number | null> {
        let res = await this.repository.update(where, partialEntity)
        if(res) {
            let raw = res.raw
            if(raw && raw.affectedRows > 0) {
                return raw.affectedRows
            }
        }
        return null
    }


    async delete(criteria: string | string[] | number | number[] | Date | Date[] | ObjectID | ObjectID[] | FindConditions<T>) : Promise<number | null> {
        let res = await this.repository.delete(criteria)
        if(res) {
            let raw = res.raw
            if(raw && raw.affectedRows > 0) {
                return raw.affectedRows
            }
        }
        return null
    }


    async queryRunnerInsert<Entity>(target: EntityTarget<Entity>, entity: QueryDeepPartialEntity<Entity> | (QueryDeepPartialEntity<Entity>[])) : Promise<number | null> {
        let res = await this.queryRunner.manager.insert<Entity>(target, entity)
        if(res) {
            let raw = res.raw
            if(raw && raw.affectedRows > 0) {
                return raw.insertId
            }
        }

        return null
    }

    async queryRunnerUpdate<Entity>(target: EntityTarget<Entity>, criteria: string | string[] | number | number[] | Date | Date[] | ObjectID | ObjectID[] | any, partialEntity: QueryDeepPartialEntity<Entity>) :Promise<number | null> {
        let res = await this.queryRunner.manager.update<Entity>(target, criteria, partialEntity)
        if(res) {
            let raw = res.raw
            if(raw && raw.affectedRows > 0) {
                return raw.affectedRows
            }
        }
        return null
    }

    async queryRunnerDelete<Entity>(targetOrEntity: EntityTarget<Entity>, criteria: string | string[] | number | number[] | Date | Date[] | ObjectID | ObjectID[] | any) : Promise<number | null>{
        let res = await this.queryRunner.manager.delete<Entity>(targetOrEntity, criteria)
        if(res) {
            let raw = res.raw
            if(raw && raw.affectedRows > 0) {
                return raw.affectedRows
            }
        }
        return null
    }

    async isExistsByWhere(where : Where<T>) :Promise<boolean>{
        let res = await this.findOne(where, [this.primaryKey])
        return res ? true : false
    }
}
